package com.credithc.signature.core.service.signprovide.zhongjin;

import com.alibaba.fastjson.JSONObject;
import com.credithc.signature.common.mapper.CertInfoMapper;
import com.credithc.signature.common.po.SealApplyInfoDTO;
import com.credithc.signature.common.po.Template;
import com.credithc.signature.common.ro.NewSignProviderReq;
import com.credithc.signature.common.ro.core.CoreRequet;
import com.credithc.signature.common.service.CFCASignService;
import com.credithc.signature.common.service.CertInfoService;
import com.credithc.signature.common.service.SignatureLogService;
import com.credithc.signature.common.service.TemplateService;
import com.credithc.signature.common.to.CFCASignParam;
import com.credithc.signature.common.to.CFCASignResult;
import com.credithc.signature.common.to.CFCASignTO;
import com.credithc.signature.common.to.CertApplyVOExt;
import com.credithc.signature.common.utils.HttpUploadUtil;
import com.credithc.signature.common.utils.MessageFilterUtil;
import com.credithc.signature.core.constant.Constant;
import com.credithc.signature.core.service.SignatureService;
import com.credithc.signature.core.service.UploadPDFService;
import com.credithc.signature.core.service.signprovide.SignatureProvider;
import com.credithc.signature.core.util.PathUtil;
import com.credithc.signature.core.util.RedisUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RLock;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import tk.mybatis.mapper.entity.Example;

import javax.annotation.Resource;
import java.io.File;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @desc: 中金签章实现类<br />
 * @author: Jingbo Hu<br/>
 * @time：2020年07⽉01⽇<br/>
 * @version：v1.0.0<br>
 */
@Slf4j
@Service("zjSignature")
public class ZhongjinProvider implements SignatureProvider {

    private static final String REDIS_KEY_PREFIX = "SIGNATURE_USERID_";

    @Value("${signature.snapSignPdf.path}")
    String snapPDFPath;

    @Value("${signature.maxBatchSignNum:160}")
    Integer maxBatchSignNum;

    @Value("${signature.redisLock.waitTime:3}")
    Integer waitTime;

    @Value("${signature.redisLock.leaseTime:6}")
    Integer leaseTime;

    @Autowired
    UploadPDFService uploadPDFService;

    @Autowired
    SignatureService signatureService;

    @Autowired
    TemplateService templateService;

    @Autowired
    CFCASignService cfcaSignService;

    @Autowired
    SignatureLogService signatureLogService;

    @Autowired
    CertInfoService certInfoService;

    @Autowired
    RedisUtils redisUtils;

    @Resource(name = "masterJdbcTemplate")
    JdbcTemplate masterJdbcTemplate;

    @Resource(name = "salveJdbcTemplate")
    JdbcTemplate salveJdbcTemplate;

    @Autowired
    CertInfoMapper certInfoMapper;


    @Override
    public String processSignature(CoreRequet coreRequet, String noSignPdfPath, List<Map<String, String>> certInfos, List<String> sealCodes) throws Exception {
        //获取要签章pdf文件路径
        File file = new File(noSignPdfPath);
        String date = file.getParentFile().getName();
        String signPdfName = Constant.SNAP_PDF_PREFIX + coreRequet.getRequestSn() + Constant.PDF_EXT;
        String signSnapPDFPath = PathUtil.getSignSnapPDFPath(snapPDFPath, signPdfName, date);
        //1.检查是否需要分开签：就是已经签完的合同，想再次签章
        NewSignProviderReq newSignProviderReq = JSONObject.parseObject(coreRequet.getJsonBody(), NewSignProviderReq.class);
        if (newSignProviderReq.getSeparateSign() != null) {
            //c.调用签公章方法
            signatureOfficialSeal(coreRequet, noSignPdfPath, signSnapPDFPath, sealCodes);
            //刪除之前的合同文件
            uploadPDFService.removeUnusedFile(noSignPdfPath);
        } else {
            //2.正常流程签署合同
            Template template = templateService.selectByTemplateNo(coreRequet.getTemplateNo());
            Integer sealType = template.getSealType();
            if (sealType.equals(Constant.PERSONAL)) {
                // 签私章
                signaturePersonalSeal(coreRequet, noSignPdfPath, certInfos, signSnapPDFPath);
            } else if (sealType.equals(Constant.OFFICAL)) {
                // 签公章
                signatureOfficialSeal(coreRequet, noSignPdfPath, signSnapPDFPath, sealCodes);
            } else if (sealType.equals(Constant.OFFICAL_PERSONAL)) {
                // 签公章和私章
                signaturePersonalAndOfficialSeal(coreRequet, noSignPdfPath, certInfos, signSnapPDFPath, sealCodes);
            } else {
                log.error("－消息－:requestSn={},非法签章类型{}", coreRequet.getRequestSn(), sealType);
                throw new Exception("非法签章类型");
            }
        }
        return signSnapPDFPath;
    }

    /**
     * 分批次签署私章
     *
     * @param requestSN
     * @param noSignPdfPath
     * @param isOfficial
     */
    private void forkSign(String requestSN, String noSignPdfPath, String signSnapPDFPath, List<CFCASignTO> sealInfoList, boolean isOfficial) throws Exception {
        int totalSealNum = sealInfoList.size();
        int loopNum = (totalSealNum % maxBatchSignNum == 0) ? totalSealNum / maxBatchSignNum : totalSealNum / maxBatchSignNum + 1;
        log.info("－消息－:requestSn={},分批处理签章,签章数据总量:{},最大盖章数为:{},分{}次盖完", requestSN, totalSealNum, maxBatchSignNum, loopNum);
        for (int index = 0; index < loopNum; index++) {
            int startIndex = index * maxBatchSignNum;
            int endIndex = (index + 1) * maxBatchSignNum > totalSealNum ? totalSealNum : (index + 1) * maxBatchSignNum;
            if (index == 0) {
                singleSign(requestSN, noSignPdfPath, signSnapPDFPath, sealInfoList.subList(startIndex, endIndex), isOfficial);
            } else {
                singleSign(requestSN, signSnapPDFPath, signSnapPDFPath, sealInfoList.subList(startIndex, endIndex), isOfficial);
            }
        }
        log.info("－消息－:requestSn={},分批处理个人签章完成", requestSN);
    }

    /**
     * 一次批量签署公私章
     *
     * @param requestSN
     * @param noSignPdfPath
     * @param signSnapPDFPath
     * @param sealInfoList
     * @param isOfficialSign
     */
    private void singleSign(String requestSN, String noSignPdfPath, String signSnapPDFPath, List<CFCASignTO> sealInfoList, boolean isOfficialSign) throws Exception {
        CFCASignParam cfcaSignParam = new CFCASignParam(sealInfoList, requestSN, noSignPdfPath, signSnapPDFPath);
        try {
            CFCASignResult cfcaSignResult;
            if (isOfficialSign) {
                cfcaSignResult = cfcaSignService.batchOfficialSign(cfcaSignParam);
            } else {
                cfcaSignResult = cfcaSignService.batchPersonalSign(cfcaSignParam);
            }
            if (!cfcaSignResult.getIsSuccess()) {
                signatureLogService.updateResult(cfcaSignResult.getResultMsg(), requestSN);
                throw new Exception(cfcaSignResult.getResultMsg());
            }
            signatureLogService.updateResult("签章成功", requestSN);
        } catch (Exception e) {
            String errorMsg = MessageFilterUtil.limitLength(e.getMessage());
            signatureLogService.updateResult(errorMsg, requestSN);
            throw new RuntimeException(e);
        }
    }

    /**
     * 签私章
     *
     * @param coreRequet
     * @param noSignPdfPath
     * @param certInfos
     * @param signSnapPDFPath
     * @throws Exception
     */
    private void signaturePersonalSeal(CoreRequet coreRequet, String noSignPdfPath, List<Map<String, String>> certInfos, String signSnapPDFPath) throws Exception {
        List<CFCASignTO> personalSealInfoList = getPersonalSealInfoList(certInfos, coreRequet.getRequestSn());
        signatureLogService.updateSealNum(coreRequet.getRequestSn(), certInfos.size(), 0);
        if (personalSealInfoList.size() > maxBatchSignNum) {
            forkSign(coreRequet.getRequestSn(), noSignPdfPath, signSnapPDFPath, personalSealInfoList, false);
        } else {
            singleSign(coreRequet.getRequestSn(), noSignPdfPath, signSnapPDFPath, personalSealInfoList, false);
        }
    }

    /**
     * 签公章
     *
     * @param coreRequet
     * @param noSignPdfPath
     * @param signSnapPDFPath
     * @param sealCodes
     * @throws Exception
     */
    private void signatureOfficialSeal(CoreRequet coreRequet, String noSignPdfPath, String signSnapPDFPath, List<String> sealCodes) throws Exception {
        List<CFCASignTO> officialSealInfoList = signatureService.getOfficialSealInfoList(coreRequet.getRequestSn(), coreRequet.getTemplateNo(), sealCodes);
        signatureLogService.updateSealNum(coreRequet.getRequestSn(), 0, officialSealInfoList.size());
        if (officialSealInfoList == null || officialSealInfoList.isEmpty()) {
            log.error("－消息－:requestSn={},公章规则号未传,不盖公章", coreRequet.getRequestSn());
            PathUtil.copy(noSignPdfPath, signSnapPDFPath);
        } else {
            singleSign(coreRequet.getRequestSn(), noSignPdfPath, signSnapPDFPath, officialSealInfoList, true);
        }
    }

    /**
     * 签私章+签公章
     *
     * @param coreRequet
     * @param noSignPdfPath
     * @param certInfos
     * @param signSnapPDFPath
     * @param sealCodes
     * @throws Exception
     */
    private void signaturePersonalAndOfficialSeal(CoreRequet coreRequet, String noSignPdfPath, List<Map<String, String>> certInfos, String signSnapPDFPath, List<String> sealCodes) throws Exception {
        List<CFCASignTO> officialSealInfoList = signatureService.getOfficialSealInfoList(coreRequet.getRequestSn(), coreRequet.getTemplateNo(), sealCodes);
        List<CFCASignTO> personalSealInfoList = getPersonalSealInfoList(certInfos, coreRequet.getRequestSn());
        signatureLogService.updateSealNum(coreRequet.getRequestSn(), personalSealInfoList.size(), officialSealInfoList.size());
        // 先盖私章
        if (personalSealInfoList.size() > maxBatchSignNum) {
            forkSign(coreRequet.getRequestSn(), noSignPdfPath, signSnapPDFPath, personalSealInfoList, false);
        } else {
            singleSign(coreRequet.getRequestSn(), noSignPdfPath, signSnapPDFPath, personalSealInfoList, false);
        }

        // 再盖公章
        if (officialSealInfoList == null || officialSealInfoList.isEmpty()) {
            log.error("－消息－:requestSn={},公章规则号未传,不盖公章", coreRequet.getRequestSn());
        } else {
            singleSign(coreRequet.getRequestSn(), signSnapPDFPath, signSnapPDFPath, officialSealInfoList, true);
        }
    }

    /**
     * 制作私章，分为更新或者新申请两个操作
     * 更新：根据与当前时间做比较查询，如果本地库中没有，但是根据用户证件号查询出来有，说明章过期，调用接口执行更新操作。
     * 新申请：如果上述条件查询出来都没有，则说明不存在，需要重新申请
     * 章有效期：私章默认是2年，公章默认2年
     *
     * @param certInfos
     * @param requestSN
     * @throws Exception
     */
    private List<CFCASignTO> getPersonalSealInfoList(List<Map<String, String>> certInfos, String requestSN) throws Exception {
        System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism", "12");

        List<CFCASignTO> collect = certInfos.parallelStream().map(jsonObj -> {

            CertApplyVOExt applyVO = JSONObject.parseObject(JSONObject.toJSONString(jsonObj), CertApplyVOExt.class);
            // 一、复制相关属性
            CFCASignTO cfcaSignTO = new CFCASignTO();
            BeanUtils.copyProperties(applyVO, cfcaSignTO);

            // 二、如果有印模图片，设置私章印模图片
            byte[] sealImageBytes = applyVO.getSealImageBytes();
            String sealImageUrl = applyVO.getSealImageUrl();
            String personalSealPos = applyVO.getPersonalSealPos();
            if (sealImageBytes != null) {
                cfcaSignTO.setSealImage(sealImageBytes);
            }
            if (StringUtils.isNotBlank(sealImageUrl)) {
                byte[] bytes = HttpUploadUtil.download(sealImageUrl);
                cfcaSignTO.setSealImage(bytes);
            }
            if (StringUtils.isNotBlank(personalSealPos)) {
                cfcaSignTO.setPersonalSealPos(personalSealPos);
            }
            return processPersonalSeal(requestSN, cfcaSignTO);
        }).collect(Collectors.toList());

        return collect;
    }

    /**
     * 制作私章流程
     *
     * @param requestSN
     * @param cfcaSignTO
     * @return
     */
    private CFCASignTO processPersonalSeal(String requestSN, CFCASignTO cfcaSignTO) {
        // 执行制章操作：分为以下三种情况
        SealApplyInfoDTO sealApplyInfo = certInfoService.getUnExpireSealInfo(cfcaSignTO.getIdTypeCode(), cfcaSignTO.getUserIdNo(), cfcaSignTO.getUserName());
        SealApplyInfoDTO sealApplyInfo2 = certInfoService.getSealInfo(cfcaSignTO.getIdTypeCode(), cfcaSignTO.getUserIdNo(), cfcaSignTO.getUserName());
        // 1、没有过期，直接使用即可
        if (Objects.nonNull(sealApplyInfo)) {
            cfcaSignTO.setSealCode(sealApplyInfo.getSealCode());
            cfcaSignTO.setSealPassword(sealApplyInfo.getSealPassword());
        }
        // 2、不存在，第一次申请
        if (Objects.isNull(sealApplyInfo) && Objects.isNull(sealApplyInfo2)) {
            String userIdNo = cfcaSignTO.getUserIdNo();
            RLock lock = redisUtils.getLock(REDIS_KEY_PREFIX + userIdNo);
            try {
                if (lock.tryLock(waitTime, leaseTime, TimeUnit.SECONDS)) {
                    SealApplyInfoDTO sealApplyInfo3 = certInfoService.getUnExpireSealInfo(cfcaSignTO.getIdTypeCode(), cfcaSignTO.getUserIdNo(), cfcaSignTO.getUserName());
                    if (Objects.nonNull(sealApplyInfo3)) {
                        cfcaSignTO.setSealCode(sealApplyInfo3.getSealCode());
                        cfcaSignTO.setSealPassword(sealApplyInfo3.getSealPassword());
                    }
                    log.info("－消息－:requestSn={},私章[不存在]第一次申请,userIdNo={},idTypeCode={}", requestSN, cfcaSignTO.getUserIdNo(), cfcaSignTO.getIdTypeCode());
                    makePersonalSeal(cfcaSignTO, requestSN);
                } else {
                    throw new RuntimeException("获取分布式锁失败......");
                }
            } catch (Exception e) {
                throw new RuntimeException(e);
            } finally {
                if (lock.isLocked() && lock.isHeldByCurrentThread()) {
                    lock.unlock();
                }
            }
        }
        // 3、已过期，需要执行更新操作
        if (Objects.isNull(sealApplyInfo) && Objects.nonNull(sealApplyInfo2)) {
            log.info("－消息－:requestSn={},私章[已过期]执行更新操作,userIdNo={},idTypeCode={}", requestSN, cfcaSignTO.getUserIdNo(), cfcaSignTO.getIdTypeCode());
            BeanUtils.copyProperties(sealApplyInfo2, cfcaSignTO);
            cfcaSignTO.setSealFlag(sealApplyInfo2.getSealFlag());
            updatePersonalSeal(cfcaSignTO, requestSN);
        }
        return cfcaSignTO;
    }


    /**
     * 制作新私章
     *
     * @param cfcaSignTO
     * @param requestSn
     * @throws Exception
     */
    private void makePersonalSeal(CFCASignTO cfcaSignTO, String requestSn) throws Exception {
        // 解决【证件重复】第一种情况
        SealApplyInfoDTO sealApplyInfo = certInfoService.getSealInfo(cfcaSignTO.getIdTypeCode(), cfcaSignTO.getUserIdNo(), cfcaSignTO.getUserName());
        if (sealApplyInfo != null) {
            cfcaSignTO.setSealCode(sealApplyInfo.getSealCode());
            cfcaSignTO.setSealPassword(sealApplyInfo.getSealPassword());
            return;
        }

        CFCASignResult cfcaSignResult = null;
        try {
            cfcaSignResult = cfcaSignService.batchMakePersonalSeal(cfcaSignTO, requestSn);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        // 处理返回结果
        if (!cfcaSignResult.getIsSuccess()) {
            // 解决【证件类型重复】问题、【重复的印章编码】问题（本地和三方seal_apply_info表没有，但是三方seal表中有时，报此错误）
            if (cfcaSignResult.getResultMsg().contains("印章申请信息（证件类型-证件号-印章标识）重复") || cfcaSignResult.getResultMsg().contains("重复的印章编码")) {
                doProcessRepetition(cfcaSignTO, requestSn);
                return;
            }
            // 其他类型的错误，抛出异常并重试
            signatureLogService.updateResult(cfcaSignResult.getResultMsg(), requestSn);
            throw new RuntimeException(cfcaSignResult.getResultMsg());
        } else {
            signatureLogService.updateResult("申请私章成功", requestSn);
            signatureLogService.insertPersonalSeal(cfcaSignTO, requestSn);
        }
    }

    /**
     * 印章申请信息（证件类型-证件号-印章标识）重复 几种情况：
     * 1、第一次申请，多线程申请，第一个线程申请成功，第二个申请失败，导致再次申请时发生重复
     * 2、未知情况导致，本地库没有数据，三方库中有数据，发生重复
     * 3、多次申请，每次证件类型不一致，例如第一次证件类型为Z，第二次证件类型为0，而本地保存的是第一次申请的数据，三方库中有2条数据，发生重复
     * 4、数据错误：相同的身份证号码，不同的姓名，导致发生重复
     * <p>
     * 解决办法：查询本地数据库，没有则执行插入操作。尽量保持和三方库数据一致
     *
     * @param cfcaSignTO
     * @param requestSn
     */
    private void doProcessRepetition(CFCASignTO cfcaSignTO, String requestSn) {
        // 解决【证件重复】第二种、第三种情况
        SealApplyInfoDTO sealApplyInfo = certInfoService.getSealInfo(cfcaSignTO.getIdTypeCode(), cfcaSignTO.getUserIdNo(), cfcaSignTO.getUserName());
        SealApplyInfoDTO sealApplyInfo2 = certInfoService.getSealInfo2(cfcaSignTO.getIdTypeCode(), cfcaSignTO.getUserIdNo());
        if (sealApplyInfo == null && sealApplyInfo2 == null) {
            signatureLogService.updateResult("申请私章成功", requestSn);
            signatureLogService.insertPersonalSeal(cfcaSignTO, requestSn);
            cfcaSignTO.setSealCode(cfcaSignTO.getSealCode());
            cfcaSignTO.setSealPassword(cfcaSignTO.getSealPassword());
        } else if (sealApplyInfo == null && sealApplyInfo2 != null) {
            // 解决【证件重复】第四种情况
            String userIdNo = cfcaSignTO.getUserIdNo();
            signatureLogService.updateResult("不同的姓名,相同的证件号码| 证件号码:" + userIdNo, requestSn);

            // 1、删除中金seal_apply_info、seal表信息和本地的seal_apply_info表信息
            String idNo = "'" + userIdNo + "'";
            StringBuilder sqlBuilder = new StringBuilder();
            sqlBuilder.append("SELECT * FROM SEAL_APPLY_INFO WHERE IDENTIFICATION_NO = ").append(idNo);
            List<SealApplyInfoDTO> dtos = salveJdbcTemplate.query(sqlBuilder.toString(), new BeanPropertyRowMapper<>(SealApplyInfoDTO.class));

            // 2、根据sealCode删除中金seal表中的数据
            if (CollectionUtils.isNotEmpty(dtos)) {
                for (SealApplyInfoDTO dto : dtos) {
                    StringBuilder sqlBuilder2 = new StringBuilder();
                    String sealCode = "'" + dto.getSealCode() + "'";
                    sqlBuilder2.append("DELETE FROM SEAL WHERE CODE = ").append(sealCode);
                    masterJdbcTemplate.update(sqlBuilder2.toString());
                }
            }

            // 3、根据userIdNo删除中金SealApplyInfo表中的数据
            StringBuilder sqlBuilder3 = new StringBuilder();
            sqlBuilder3.append("DELETE FROM SEAL_APPLY_INFO WHERE IDENTIFICATION_NO = ").append(idNo);
            masterJdbcTemplate.update(sqlBuilder3.toString());

            // 4、根据userIdNo删除签章SealApplyInfo表中的数据
            Example example = new Example(SealApplyInfoDTO.class);
            Example.Criteria criteria = example.createCriteria();
            criteria.andEqualTo("identificationNo", userIdNo);
            certInfoMapper.deleteByExample(example);

            // 5、抛出异常重试
            throw new RuntimeException("不同的姓名,相同的证件号码| 证件号码:" + userIdNo);
        }
    }


    /**
     * 更新私章
     *
     * @param requestSn
     * @param cfcaSignTO
     */
    private void updatePersonalSeal(CFCASignTO cfcaSignTO, String requestSn) {
        CFCASignResult cfcaSignResult = null;
        try {
            cfcaSignResult = cfcaSignService.updatePersonalSeal(cfcaSignTO, requestSn);
        } catch (Exception e) {
            log.error("执行更新私章失败......", e);
        }
        if (!cfcaSignResult.getIsSuccess()) {
            signatureLogService.updateResult(cfcaSignResult.getResultMsg(), requestSn);
            throw new RuntimeException(cfcaSignResult.getResultMsg());
        } else {
            signatureLogService.updateResult("更新私章成功", requestSn);
            certInfoService.updateCertDate(requestSn, cfcaSignTO);
            log.info("－消息－: requestSn={},过期私章更新成功| 证件号码:{}", requestSn, cfcaSignTO.getUserIdNo());
        }
    }

}
